/**
 * Copyright 2004 Cordys R&D B.V. 
 * 
 * This file is part of the Cordys FTP Connector. 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 package com.eibus.applicationconnector.ftp;

import java.io.File;

import com.eibus.soap.fault.Fault;
import com.eibus.util.logger.CordysLogger;
import com.eibus.util.logger.Severity;
import com.eibus.xml.nom.Document;
import com.eibus.xml.nom.Node;
import com.eibus.xml.xpath.XPath;
import com.eibus.applicationconnector.ftp.localization.message.Messages;
import com.eibus.directory.soap.DirectoryException;
import com.eibus.exception.ExceptionGroup;

/**
 * This class is the handler for FTP's PUT command
 */
class UpLoad extends FTPCommand
{
	boolean sync = false;
	private int publishNode;
    private Document document;
    private int responseFilesNode;
    
    private String basepath = "";
    private String FILE_PATH_TAG = "file";
    private static final String STR_FILES = "files";
    public static final String PASSIVE_ELEMENT = "passive";
    private static final CordysLogger logger = CordysLogger.getCordysLogger(UpLoad.class);
    
    @Override
	public boolean prepare()
    {
        if (Node.getName(requestXML).equals("multipleUpload"))
        {
        	FILE_PATH_TAG = "path";
        }
        else
        {
        	FILE_PATH_TAG = "file";
        }
        
        document   = Node.getDocument(requestXML);
        responseFilesNode = Node.duplicate(Node.getElementNS(requestXML, STR_FILES, getNameSpace()));
        
        if( responseFilesNode == 0 )
        {
        	logger.log(Severity.ERROR, "<files> node is not available in the upload method.");
            Fault fault = response.createSOAPFault(Fault.Codes.CLIENT,Messages.FILES_NOT_SPECIFIED_UPLOAD);
            addFaultDetails2(fault.getDetail(),null,DEFAULT_QNAME,null,null);
            return false;
        }
        
        int[] fileNodes = XPath.getMatchingNodes(FILE_PATH_TAG, null, responseFilesNode);
        if( fileNodes.length == 0 )
        {
        	logger.log(Severity.ERROR, "<file> node is not available in the upload method.");
        	Fault fault = response.createSOAPFault(Fault.Codes.CLIENT,Messages.FILES_NOT_SPECIFIED_UPLOAD);
        	addFaultDetails2(fault.getDetail(),null,DEFAULT_QNAME,null,null);
        	return false;
        }
        
        boolean soapFault = false;
        
        for (int index = 0; index < fileNodes.length; index ++)
        {
        	int sourceFileNode = XPath.getFirstMatch(".//source", null, fileNodes[index]);
        	int targetFileNode = XPath.getFirstMatch(".//target", null, fileNodes[index]);
        	
        	String sourceFile = Node.getDataWithDefault(sourceFileNode, "").trim();
        	String targetFile = Node.getDataWithDefault(targetFileNode, "").trim();
        	
        	if(sourceFile.length() == 0)
            {
        		logger.log(Severity.ERROR, "Source file is not available in the upload method.");
        		Fault fault = response.createSOAPFault(Fault.Codes.CLIENT,Messages.SOURCE_NOT_SPECIFIED);
        		addFaultDetails2(fault.getDetail(),null,DEFAULT_QNAME,null,null);
        		return false;
            }
        	
        	if (ftpConfiguration.getBasepath() == null && targetFile.length() == 0 )
        	{
        		logger.log(Severity.ERROR, "Target file is not available in the upload method.");
        		Fault fault = response.createSOAPFault(Fault.Codes.CLIENT,Messages.TARGET_FILE_NOT_SPECIFIED);
        		addFaultDetails2(fault.getDetail(), null, DEFAULT_QNAME, null, null);
        		return false;
        	}
        	
            sourceFile = getProperFileSeparator(sourceFile, false);
            File file = null;
            
            if (ftpConfiguration.getBasepath() == null)
    		{
            	file = new File(sourceFile);
    		}
            else
            {
            	basepath = ftpConfiguration.getBasepath();
            	logger.log(Severity.DEBUG, "Basepath is set to " + basepath);
                /*
                 * Source file path should not contain ".", ".." or ":"
                 */
                if (!validate(sourceFile))
                {
                	response.createSOAPFault(Fault.Codes.CLIENT, Messages.INVALID_SOURCE);
                    return false;
                }
                
                file = new File(basepath + "/" + sourceFile);
            }
            if (!file.isFile())
            {
            	soapFault = true;
            	document.createTextElement("status", "The system cannot find the file specified or the access is denied.", fileNodes[index]);
            	logger.log(Severity.ERROR, "Cannot find the file specified: " + file.getAbsolutePath());
            	continue;
            }
            
    		if (!ftpConfiguration.getProtocol().equals("SFTP"))
    		{
    			document.createTextElement("statuscode", "", fileNodes[index]);
    		}
    		
    		document.createTextElement("status", inProgressMesg, fileNodes[index]);
        }
        
        if (soapFault)
        {
        	for (int index = 0; index < fileNodes.length; index ++)
            {
        		int statusMessageNode = XPath.getFirstMatch("status", null, fileNodes[index]);
            	if (Node.getDataWithDefault(statusMessageNode, "").equals(inProgressMesg))
            	{
            		Node.setDataElement(Node.getParent(statusMessageNode), "status", "");
            	}
            }
        		
        	Fault fault = response.createSOAPFault(Fault.Codes.CLIENT, Messages.SPECIFIED_FILE_NOT_FOUND);
        	fault.getDetail().addDetailEntry(responseFilesNode);
            return false;                	
        }
        
        publishNode = document.createElementNS("upload",null,null,getNameSpace(),0);
        addRequestDetailsToResponse(requestXML, publishNode);
        
        int pFilesNode = document.createTextElement(STR_FILES,null,publishNode);
        Node.appendToChildren(pFilesNode,publishNode);
        return true;
    }

	@Override
	public void commit()
    {
		try
		{
	    	Thread uploadThread = new Thread(new Uploader(responseFilesNode, Node.duplicate(publishNode)));
	    	uploadThread.start();
	    	
	    	int responseNode = response.getXMLNode();
	    	sync = Node.getDataWithDefault(Node.getElementNS(requestXML,"synchronous",getNameSpace()),"false").equals("true");
	    	if(sync)
	        {
	        	try
	        	{
	        		uploadThread.join();
				}
	        	catch (InterruptedException e)
				{
					logger.log(Severity.ERROR, "An exception has occured while downloading " + e.getMessage(), e);
				}
	        }
	    	Node.appendToChildren(Node.duplicate(responseFilesNode), responseNode);
		} finally
		{
			Node.delete(publishNode);
			publishNode = 0;
			
			if(sync)
			{
				Node.delete(responseFilesNode);
				responseFilesNode = 0;
			}
		}
    }

    private class Uploader implements Runnable
    {
        private int uploaderFilesNode;
        private int uploaderPublishNode;
        
        private String type;
        private String ftpMessage;
        
        Uploader(int filesNode, int publishNode)
        {
        	this.uploaderFilesNode = filesNode;
        	this.uploaderPublishNode =publishNode;
        }
        
        @Override
		public void run()
		{
        	try
        	{
	        	int[] fileNodes = XPath.getMatchingNodes(FILE_PATH_TAG, null, uploaderFilesNode);
	        	for (int index = 0; index < fileNodes.length; index ++)
	        	{
	        		int statuscode = Node.getElement(fileNodes[index], "statuscode");
	        		int statusNode = Node.getElement(fileNodes[index], "status");
	            	this.type = Node.getAttribute(fileNodes[index], "type", "ascii");
	            	
	                if(this.type.equals("ascii"))
	                {
	                	adapter.setFileType(IFTPClient.FILE_TYPE.ASCII);
	    			}
	
	                if(this.type.equals("bin"))
	                {
	                	adapter.setFileType(IFTPClient.FILE_TYPE.BINARY);
	    			}
	
	            	String source = Node.getDataWithDefault(XPath.getFirstMatch("source", null, fileNodes[index]), "");
	            	String target = Node.getDataWithDefault(XPath.getFirstMatch("target", null, fileNodes[index]), "");
	            	
	            	source = getProperFileSeparator(source, false);
	            	target = getProperFileSeparator(target, true);
	            	
	            	if (ftpConfiguration.getBasepath() != null)
	            	{
	            		source = basepath + "/" + source;	//NOPMD
	            		
	            		if (target.equals("") || target== null)
	            		{
	            			target = source.substring(source.lastIndexOf("/")+1);
	            		}
	            	}
	                
	            	try
	            	{
	            		ftpMessage = adapter.upload(source, target);
		        	} catch (FTPException ftpe)
		        	{
		        		adapter.disconnect();
		        		logger.log(Severity.ERROR, "An error has occurred while uploading file. " + ftpe.getMessage());
		                Fault fault = response.createSOAPFault(Fault.Codes.SERVER, Messages.UPLOAD_FAILURE, new String[]{ftpe.getLocalizedMessage()});
		                addFaultDetails2(fault.getDetail(), null, DEFAULT_QNAME, null, null);
		                return;
		        	}
	            	
	        		if (ftpConfiguration.getProtocol().equals("SFTP"))
	        		{
	        			Node.setDataElement(Node.getParent(statusNode), "status", ftpMessage);
	        			Node.appendToChildren(statusNode, fileNodes[index]);
	        		}
	        		else
	        		{
	                	Node.setDataElement(Node.getParent(statuscode), "statuscode", ftpMessage.substring(0, 3));
	                    Node.setDataElement(Node.getParent(statusNode), "status", ftpMessage.substring(4));
	                    Node.appendToChildren(statuscode, fileNodes[index]);
	        			Node.appendToChildren(statusNode, fileNodes[index]);			
	        		}
	    			
	    			int pfilesNode = Node.getElementNS(uploaderPublishNode, STR_FILES, getNameSpace());
	    			Node.appendToChildren(Node.duplicate(fileNodes[index]), pfilesNode);
	    			try
	    			{
						publishFTPMessage(ftpNotificationSubject, Node.duplicate(this.uploaderPublishNode));
					}
	    			catch (ExceptionGroup e)
	    			{
	    				logger.log(Severity.ERROR, e.getMessage(), e);
					}
	    			catch (DirectoryException e)
	    			{
	    				logger.log(Severity.ERROR, e.getMessage(), e);
					}
	        	}
	        	
	        	/* After the command is complete, disconnect the adapter from the FTP server */
	        	adapter.disconnect();
        	} finally
        	{
        		Node.delete(uploaderPublishNode);
        		uploaderPublishNode = 0;
        		
        		if(!sync)
        		{
        			Node.delete(uploaderFilesNode);
        			uploaderFilesNode = 0;
        		}
        	}
		}
    }
}